﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using iTechnology.Tools.Entities;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Xml;
using System.IO;

namespace iTechnology.Tools.Sharepoint
{
    /// <summary>
    /// SharePoint helper 
    /// </summary>
    internal class SharePointHelper
    {
        #region Constants
        public readonly string SITE_IMAGE_URL = "ITS16.GIF";
        public readonly string WEB_APPLICATION_IMAGE_URL = "coll_site.gif";
        public readonly string WEB_IMAGE_URL = "titlegraphic.gif";
        public readonly string FARM_IMAGE_URL = "ltdcl.gif";
        public readonly string ERROR_IMAGE_URL = "ServiceNotInstalled.gif";
        #endregion

        #region Properties
        /// <summary>
        /// Checks if the Microsoft.SharePoint assembly exists on the machine. 
        /// </summary>
        public bool IsSharepointExist
        {
            get
            {
                try
                {
                    //Check if the Microsoft.SharePoint assembly exists in the GAC. This will help up show the right message to the user
                    //when it can not find SharePoint installed on the server. 
                    Assembly assembly =  Assembly.ReflectionOnlyLoad("Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c") ;
                    return assembly != null && assembly.GlobalAssemblyCache;
                }
                catch
                {
                    return false;
                }
            }
        }

        private SPWebService contentServiceField;
        /// <summary>
        /// Get the content service of the local farm
        /// </summary>
        public SPWebService ContentService
        {
            get
            {
                if (this.contentServiceField == null)
                {
                    if (this.Farm != null)
                        this.contentServiceField = this.Farm.Services.GetValue<SPWebService>("");
                }
                return this.contentServiceField;
            }
        }

        private SPFarm farmField;
        /// <summary>
        /// Gets the SharePoint farm on the machine where the tool is being executed. 
        /// </summary>
        public SPFarm Farm
        {
            get
            {
                if (this.farmField == null)
                {
                    this.farmField = SPFarm.Local;
                }
                return farmField;
            }
        }

        private List<SPWebApplication> webApplicationsField;
        /// <summary>
        /// Gets the list of all the web application from the content service of the local farm. 
        /// </summary>
        public List<SPWebApplication> WebApplications
        {
            get
            {
                
                if (this.webApplicationsField == null)
                {
                    if (ContentService != null)
                        this.webApplicationsField = new List<SPWebApplication>(ContentService.WebApplications);
                   
                }
                return this.webApplicationsField;
            }
        }
        #endregion

        #region Helper Methods

        public void ClearCachedItems()
        {
            listTemplates.Clear();
        }
        #endregion

        #region SharePoint Hierarchy Helper Methods
        /// <summary>
        /// Gets a tree node that represents SharePoint site
        /// </summary>
        /// <param name="web">SharePoint site</param>
        /// <param name="orphanLists">Collection with the site orphaned lists</param>
        /// <returns>Tree node that represents SharePoint site</returns>
        public TreeNode GetWebNode(SPWeb web, out List<OrphanList> orphanLists)
        {
            TreeNode newNode = new TreeNode();
            return GetWebNode(web,newNode, out orphanLists);
        }
        /// <summary>
        /// Gets a tree node that represents SharePoint site
        /// </summary>
        /// <param name="web">SharePoint site</param>
        /// <param name="treeNode">Tree node to be updated</param>
        /// <param name="orphanLists">Collection with the site orphaned lists</param>
        /// <returns>Tree node that represents SharePoint site</returns>
        public TreeNode GetWebNode(SPWeb web, TreeNode treeNode, out List<OrphanList> orphanLists)
        {
            orphanLists = null;
            try
            {
                //Get the collection with the orphan lists and create a tree node that contains information
                //about provided SharePoint site. The collection with the orhpan lists is needed to show how many lists
                //are missing their template out of all the site lists. 
                orphanLists = FindOrphanLists(web);

                treeNode.Text = string.Format("{0} [{1}/{2}]", web.Title, orphanLists.Count, web.Lists.Count);
                treeNode.Tag = web;
                treeNode.ImageKey = (orphanLists.Count != 0) ? ERROR_IMAGE_URL : WEB_IMAGE_URL;
                treeNode.SelectedImageKey = treeNode.ImageKey;
            }
            catch (UnauthorizedAccessException ex)
            {
                //If the user doesn't have enough rights on the site, catch the exception
                //and show and indicator about that. 
                treeNode.Text += " [access denied]";
            }

            return treeNode;
        }
        /// <summary>
        /// Build the tree that represents the hierarchy of the local farm - web applications, site collections, sites
        /// </summary>
        /// <returns></returns>
        public TreeNode BuildTree()
        {
            //Create the root node of the tree. Check if there is SharePoint on the server where the tool is being run. If there is not such just show an appropriate message. 
            TreeNode rootNode = (this.IsSharepointExist && this.Farm != null) ? new TreeNode(string.Format("Sharepoint Farm [{0}]", this.Farm.Name)) : new TreeNode("Unable to find Sharepoint Farm.");
            rootNode.ImageKey = FARM_IMAGE_URL;
            rootNode.SelectedImageKey = FARM_IMAGE_URL;
            rootNode.Expand();
            if (this.WebApplications != null)
            {
                //Iterate through all the web applications of the local farm and create tree nodes for them
                foreach (SPWebApplication webApplication in this.WebApplications)
                {
                    TreeNode webApplicationNode = new TreeNode(webApplication.Name);
                    webApplicationNode.Tag = webApplication;
                    webApplicationNode.ImageKey = WEB_APPLICATION_IMAGE_URL;
                    webApplicationNode.SelectedImageKey = WEB_APPLICATION_IMAGE_URL;
                    //Run the code with elevated privilidges in order to try to get information event about
                    //the SharePoint sites that the current user is not member of. If this is not possible the [access denied]
                    //indicator will be shown. 
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    //Iterate through all the site collections of the current application and create tree nodes for them. 
                    foreach (SPSite site in webApplication.Sites)
                    {
                        TreeNode siteNode = new TreeNode(site.Url);
                        siteNode.Tag = site;
                        siteNode.ImageKey = SITE_IMAGE_URL;
                        siteNode.SelectedImageKey = siteNode.ImageKey;
                        try
                        {
                           //Iterate through all the sites in the current site collection and create tree nodes for them. 
                            foreach (SPWeb web in site.AllWebs)
                            {

                                List<OrphanList> orhpanLists = null;
                                siteNode.Nodes.Add(GetWebNode(web, out orhpanLists));
                            }
                        }
                        catch (UnauthorizedAccessException ex)
                        {
                            //if the user doesn't have permission for the current site collection show an indicator
                            //in the tree node. 
                            siteNode.Text += " [access denied]";
                        }
                        webApplicationNode.Nodes.Add(siteNode);
                    }
                });
                    rootNode.Nodes.Add(webApplicationNode);
                }
            }
            return rootNode;
        }
        #endregion

        private List<ListTemplate> listTemplates = new List<ListTemplate>();
        /// <summary>
        /// Get all the list templates in the provides SharePoint site - activated or not. 
        /// </summary>
        /// <param name="web">A SharePoint site where to look for the templates. </param>
        /// <returns>Returns a collection with site list templates</returns>
        public List<ListTemplate> FindListTemplates(SPWeb web)
        {
            if (listTemplates.Count == 0)
            {
                Dictionary<Guid, ListTemplate> result = new Dictionary<Guid, ListTemplate>();
                Dictionary<Guid, string> templateInstances = new Dictionary<Guid, string>();

                ListTemplate emptyTemplate = new ListTemplate();
                emptyTemplate.Name = "None";
                emptyTemplate.FeatureId = Guid.Empty;
                result.Add(emptyTemplate.FeatureId, emptyTemplate);

                //In order to take all the list templates for the site (activated or not) we need to access the farm definitions and not the site ones, since the site ones
                //will give us only the activated ones. 
                foreach (SPFeatureDefinition definition in this.Farm.FeatureDefinitions)
                {
                    if (definition.Scope == SPFeatureScope.Web)
                    {
                        SPElementDefinitionCollection elementDefinitions = definition.GetElementDefinitions(System.Threading.Thread.CurrentThread.CurrentCulture);
                        foreach (SPElementDefinition elementDefinition in elementDefinitions)
                        {
                            //If the type of the element definition is list instance we need to store it for later when we will populate the collection of lists
                            //that a particular list template was used for. We need the Url attribute of the list so we can match the list template later. 
                            if (elementDefinition.ElementType.ToLower() == "listinstance")
                            {

                                XmlNode featureIdAttribute = elementDefinition.XmlDefinition.Attributes.GetNamedItem("FeatureId");
                                if (featureIdAttribute != null)
                                {
                                    Guid featureId = new Guid(elementDefinition.XmlDefinition.Attributes.GetNamedItem("FeatureId").Value);
                                    XmlNode listUrlNode = elementDefinition.XmlDefinition.Attributes.GetNamedItem("Url");
                                    string listUrl = string.Empty;
                                    if (listUrlNode != null)
                                        listUrl = listUrlNode.Value;

                                    if (!string.IsNullOrEmpty(listUrl))
                                    {
                                        if (!templateInstances.ContainsKey(featureId))
                                            templateInstances[featureId] = string.Empty;

                                        templateInstances[featureId] += listUrl.ToLower() + ";";
                                    }
                                }
                            }
                            //the type of the element definition that represents a list template is 'listtemplate', so we need all of them. 
                            else if (elementDefinition.ElementType.ToLower() == "listtemplate")
                            {

                                ListTemplate listTemplate = new ListTemplate();
                                listTemplate.FeatureId = definition.Id;
                                listTemplate.Name = definition.GetTitle(System.Threading.Thread.CurrentThread.CurrentCulture);
                                result.Add(listTemplate.FeatureId, listTemplate);
                                break;
                            }
                        }

                    }
                }

                //Populate the List collection of the list template with all the list instances that were created from it. 
                foreach (Guid featureId in templateInstances.Keys)
                {
                    if (result.ContainsKey(featureId) && !string.IsNullOrEmpty(templateInstances[featureId]))
                        result[featureId].Lists.AddRange(templateInstances[featureId].Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries));
                }

                this.listTemplates = (from ListTemplate lt in result.Values.ToList() orderby lt.Name ascending select lt).ToList();
            }
            return this.listTemplates;
            

        }
        /// <summary>
        /// Find a list template that can be a possible new template for the provided orphan list. 
        /// </summary>
        /// <param name="web">The SharePoint site where to look for the list templates</param>
        /// <param name="list">An orhpan list</param>
        /// <returns>Returns the feature if of the list template that is eventual match, or empty Guid if no such is found. </returns>
        public Guid FindMatchingListTemplate(SPWeb web, SPList list)
        {
            //Get all the available list in the provided SharePoint site
            List<ListTemplate> templates = FindListTemplates(web);

            //Get all the list templates that have the list url as a possibility. Every list template entity has a collection of all
            //the lists that are created from it. 
            var result = from ListTemplate lt in templates where lt.Lists.Contains(list.RootFolder.Url.ToLower()) select lt;
            if (result.Count() > 0)
                return result.First().FeatureId;

            return Guid.Empty;

        }

        /// <summary>
        /// Find all the list instances that have a list template that is not defined in SharePoint anymore. 
        /// </summary>
        /// <param name="web">The SharePoint site where to look in. </param>
        /// <returns>Collection with problematic list instances. </returns>
        public  List<OrphanList> FindOrphanLists(SPWeb web)
        {
            //ALGORYTHM: All the lists instances in the provided site that have value in the TemplateFeatureId property
            // and this value doesn't exist in the list templates (activated or not) of the provide site are considered orphaned. This means
            // that the liste template that was used when they were created doesn't exist anymore. 
            List<OrphanList> result = new List<OrphanList>();
            if (web != null)
            {
                List<ListTemplate> templates = FindListTemplates(web);
                    
                foreach (SPList list in web.Lists)
                {
                    
                    if (list.TemplateFeatureId != Guid.Empty )
                    {
                        var matchingListTemplates = from ListTemplate lt in templates where lt.FeatureId == list.TemplateFeatureId select lt;
                        if (matchingListTemplates.Count() == 0)
                        {
                            OrphanList orphanList = new OrphanList();
                            orphanList.ListName = list.Title;
                            orphanList.ListId = list.ID;
                            orphanList.SelectedTemplateFeatureId = FindMatchingListTemplate(web, list);
                            orphanList.Selected = orphanList.SelectedTemplateFeatureId != Guid.Empty;
                            result.Add(orphanList);
                        }
                    }
                }
            }
            return result;
        }
    }
}
